using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Framework
{
    public interface ISocketHeadReader
    {
        //消息头大小 如果<1 则收取socket所有数据
        int GetHeadSize();
        /// <summary>
        /// 读消息头，dataSize为除头外数据长度
        /// </summary>
        /// <param name="buffer">头文件数据</param>
        /// <param name="id">自增id</param>
        /// <param name="dataSize">数据体长度</param>
        void ReadHead(byte[] buffer, int id, out int dataSize);

    }
    public class SocketNetData
    {
        public int ID;
        public int Size;
        public byte[] Data;
    }

    public interface ISocketNetDataProvider
    {
        //获取一个消息包,可实现对象池
        SocketNetData GetSocketNetData(int size);
        //释放一个消息包的引用
        void ReleaseSocketNetData(SocketNetData socketNetData);
    }

    public partial class SocketClient
    {
        internal enum NetStatus
        {
            None,
            Wait,
            DNSQuery,
            Connect,
            ConnectSuccess,
            Connected,
            ConnectFail,
            Network,
            Close,
        }
        internal enum ErrorCode
        {
            None,
            ReceiveError,
            SendError,
            Timeout,
            Close,
        }
        internal class DefaultHeadReader : ISocketHeadReader
        {
            private int _headSize = sizeof(ushort);
            private int _dataSize;
            public DefaultHeadReader()
            {

            }
            public int GetHeadSize()
            {
                return _headSize;
            }
            public void ReadHead(byte[] buffer, int id, out int dataSize)
            {
                dataSize = BitConverter.ToUInt16(buffer, 0);
                //map id to bufferdata
            }
        }
        public int SendSize = 1400;
        private byte[] socket_recvBuffer;
        private byte[] m_HeadBuffer;
        private byte[] m_sendBuffer = new byte[10240];
        private readonly FlexBuffer _recvBuffer = new(10240);
        private readonly FlexBuffer _sendBuffer = new(10240);
        private readonly Queue<SocketNetData> _recvList = new Queue<SocketNetData>();
        private readonly Queue<SocketNetData> _sendList = new Queue<SocketNetData>();


        public string Host { get; private set; }
        public int Port { get; private set; }
        public IPAddress ConnectIP { get; private set; }
        public bool Connected { get { return _status == NetStatus.Network; } }
        public int Priority => 0;
        public Action<SocketNetData> OnReceiveData;
        public Action<SocketNetData> OnSendData;
        public Action OnConnect;
        public Action OnConnectFail;
        public Action OnDisconnect;
        public Action OnError;
        private NetStatus _status;
        private TcpClient _socket;
        private ErrorCode _errorCode;
        private ISocketNetDataProvider _netdataProvider;
        private DefaultHeadReader _defaultHeadReader = new DefaultHeadReader();
        private ISocketHeadReader _headReader;
        private int _netDataId = 0;
        private bool _sending;
        private bool _receiveing;
        private float _connectTimeout;
        private float _connectTime;
        private bool ThreadMode;

        public virtual void Connect(string host, int port, float timeout = 5)
        {
            socket_recvBuffer = new byte[1024 * 1024];
            Host = host;
            Port = port;
            _status = NetStatus.DNSQuery;
            if (timeout < 5f)
            {
                timeout = 5f;
            }
            _connectTimeout = timeout;
            _connectTime = _connectTimeout;
            ThreadMode = false;
            Log("connect timeout " + _connectTimeout);
            if (_netdataProvider == null)
            {
                LogError("设置 SetNetDataProvider 利用消息内容");
            }
        }
        public virtual void Send(byte[] data, int length = 0)
        {
            lock (_sendList)
            {
                var value = new SocketNetData();
                value.Data = data;
                if (length < 1)
                {
                    length = data.Length;
                }
                value.Size = length;
                _sendList.Enqueue(value);
            }
        }
        public virtual void Send(SocketNetData data)
        {
            //配合 OnSendData 回收数据
            lock (_sendList)
            {
                _sendList.Enqueue(data);
            }
        }
        public void SetNetDataProvider(ISocketNetDataProvider netdataProvider)
        {
            _netdataProvider = netdataProvider;
        }
        public void SetHeadReader(ISocketHeadReader headReader)
        {
            _headReader = headReader;
        }
        public virtual void Close()
        {
            if (_status != NetStatus.Connected && _status != NetStatus.Network)
            {
                return;
            }
            OnSocketError(ErrorCode.Close);
            OnDisconnect?.Invoke();
        }


        public virtual void Update(float deltaTime)
        {
            if (_status == NetStatus.DNSQuery)
            {
                _status = NetStatus.Wait;
                BeginGetHostAddress();
            }
            else if (_status == NetStatus.Connect)
            {
                _status = NetStatus.Wait;
                BeginConnect();
            }
            else if (_status == NetStatus.ConnectSuccess)
            {
                Log("ConnectSuccess");
                _status = NetStatus.Connected;
            }
            else if (_status == NetStatus.Connected)
            {
                _status = NetStatus.Network;
                OnConnected();
            }
            else if (_status == NetStatus.Network)
            {
                OnNetwork();
            }
            if (_connectTime > 0f)
            {
                _connectTime -= deltaTime;
                if (_connectTime < 0f)
                {
                    LogError("连接超时");
                    OnSocketError(ErrorCode.Timeout);
                }
            }
        }
        private SocketNetData getNetData(int size)
        {
            SocketNetData netdata = null;
            if (_netdataProvider != null)
            {
                netdata = _netdataProvider.GetSocketNetData(size);
                if (netdata.Data == null || netdata.Data.Length < size)
                {
                    netdata.Data = new byte[size];
                }
                netdata.Size = size;
            }
            else
            {
                netdata = new SocketNetData
                {
                    Size = size,
                    Data = new byte[size]
                };
            }
            return netdata;
        }
        private void LogError(string message)
        {

            FrameworkLog.LogError(message);
        }
        private void Log(string message)
        {
            FrameworkLog.Log(message);
        }

        private void OnSocketError(ErrorCode code)
        {
            LogError("OnSocketError " + code.ToString());
            _status = NetStatus.Close;
            _recvBuffer.Clear();
            _sendBuffer.Clear();
            _sendList.Clear();
            _recvList.Clear();
            _sending = false;
            _receiveing = false;
            StopSenderThread();
            StopReceiverThread();
            _errorCode = code;
            if (_socket != null)
            {
                if (_socket.Connected)
                {
                    try
                    {
                        _socket.GetStream().Close();
                        _socket.Close();
                    }
                    catch { }
                }
                try
                {
                    _socket.Close();
                }
                catch { }
                _socket = null;
            }
            this.OnError?.Invoke();
        }
        private void BeginGetHostAddress()
        {
            try
            {
                Dns.BeginGetHostAddresses(Host, EndGetHostAddress, null);
            }
            catch (Exception ex)
            {
                if (OnConnectFail != null)
                {
                    OnConnectFail.Invoke();
                }
                LogError(ex.ToString());
            }
        }

        private void EndGetHostAddress(IAsyncResult asyncResult)
        {
            IPAddress[] addresses;
            try
            {
                addresses = Dns.EndGetHostAddresses(asyncResult);
            }
            catch (Exception ex)
            {
                if (OnConnectFail != null)
                {
                    OnConnectFail.Invoke();
                }
                LogError(ex.ToString());
                return;
            }
            if (addresses[0].AddressFamily == AddressFamily.InterNetworkV6)
            {
                Log("InterNetworkV6");
            }
            else
            {
                Log("InterNetworkV4");
            }
            ConnectIP = addresses[0];
            _status = NetStatus.Connect;
        }

        private void BeginConnect()
        {
            Log("BeginConnect");
            _connectTime = _connectTimeout;
            _socket = new TcpClient()
            {
                NoDelay = true
            };
            _socket.ReceiveTimeout = 10;
            try
            {
                _socket.BeginConnect(ConnectIP, Port, EndConnect, this);
            }
            catch (Exception ex)
            {
                LogError(ex.ToString());
                OnConnectFail?.Invoke();
            }
        }

        private void EndConnect(IAsyncResult asyncResult)
        {
            Log("EndConnect");
            _connectTime = -1f;
            if (_socket == null) return;
            try
            {
                _socket.EndConnect(asyncResult);
                Log("EndConnect " + _socket.Connected);
                if (_socket.Connected)
                {
                    var stream = _socket.GetStream();
                    if (stream != null)
                    {
                        _status = NetStatus.ConnectSuccess;
                    }
                    else
                    {
                        LogError("_socket.GetStream() is null");
                        OnConnectFail?.Invoke();
                    }
                }
                else
                {
                    _status = NetStatus.ConnectFail;
                    LogError("_socket.Connected is false");
                    OnConnectFail?.Invoke();
                }
            }
            catch (Exception ex)
            {

                OnConnectFail?.Invoke();
                LogError(ex.ToString());
                return;
            }
            _recvList.Clear();
            _sendBuffer.Clear();
            _recvBuffer.Clear();
            _errorCode = ErrorCode.None;
            _netDataId = 0;
        }

        private void OnConnected()
        {
            OnConnect?.Invoke();
            if (ThreadMode)
            {
                StartSenderThread();
                StartReceiverThread();
            }
            else
            {

            }
        }

        //receive
        private void BeginRecevice()
        {
            if (_receiveing)
            {
                return;
            }
            if (!_socket.Connected)
            {
                LogError("_socket.Connected is false");
                OnSocketError(ErrorCode.ReceiveError);
                return;
            }
            if (_socket.GetStream() == null)
            {
                LogError("_socket.GetStream() is null");
                OnSocketError(ErrorCode.ReceiveError);
                return;
            }
            _receiveing = true;
            try
            {
                if (_socket.GetStream().CanRead)
                {
                    _socket.GetStream().BeginRead(socket_recvBuffer, 0, socket_recvBuffer.Length, new AsyncCallback(EndRecevice), this);
                }
            }
            catch (Exception ex)
            {
                _receiveing = false;
                OnSocketError(ErrorCode.ReceiveError);
                LogError(ex.ToString());
            }
        }
        private void EndRecevice(IAsyncResult asyncResult)
        {
            _receiveing = false;
            if (_socket == null)
            {
                LogError("_socket is null");
                return;
            }
            if (!_socket.Connected)
            {
                LogError("_socket.Connected is false");
                OnSocketError(ErrorCode.ReceiveError);
                return;
            }
            if (_socket.GetStream() == null)
            {
                LogError("_socket.GetStream() is null");
                OnSocketError(ErrorCode.ReceiveError);
                return;
            }

            int recvSize = 0;

            try
            {
                recvSize = _socket.GetStream().EndRead(asyncResult);
            }
            catch (Exception e)
            {
                LogError(e.ToString());
                OnSocketError(ErrorCode.ReceiveError);
                return;
            }
            if (recvSize <= 0)
            {
                LogError("接收到的消息包长度为0");
                OnSocketError(ErrorCode.ReceiveError);
                return;
            }
            try
            {
                ReceiveData(recvSize);
            }
            catch (Exception ex)
            {
                LogError(ex.ToString());
                OnSocketError(ErrorCode.ReceiveError);
            }
            BeginRecevice();
        }

        protected virtual void ReceiveData(int size)
        {
            if (!_recvBuffer.Write(socket_recvBuffer, size))
            {
                return;
            }
            var headReader = _headReader;
            if (headReader == null)
            {
                headReader = _defaultHeadReader;
            }
            var headSize = headReader.GetHeadSize();
            if (headSize <= 0)
            {
                pushNetData(_recvBuffer.Length);
                return;
            }
            if (m_HeadBuffer == null || headSize > m_HeadBuffer.Length)
            {
                m_HeadBuffer = new byte[headSize];
            }
            try
            {
                while (_recvBuffer.Read(m_HeadBuffer, headSize))
                {
                    int dataSize;
                    int id = _netDataId++;
                    headReader.ReadHead(m_HeadBuffer, id, out dataSize);
                    if (dataSize < 1 || dataSize > 1024 * 32)
                    {
                        LogError("Recv data size error " + dataSize);
                    }
                    if (_recvBuffer.Length < dataSize + headSize)
                    {
                        _netDataId--;
                        break;
                    }
                    _recvBuffer.Delete(headSize);
                    var data = pushNetData(dataSize);
                    if (data != null)
                    {
                        data.ID = id;
                    }
                }
            }
            catch (Exception e)
            {
                LogError(e.ToString());
            }

        }
        private SocketNetData pushNetData(int dataSize)
        {
            if (dataSize < 1 || dataSize > socket_recvBuffer.Length)
            {
                LogError("SocketNetData.Data 为空或长度出错 " + dataSize);
                return null;
            }
            SocketNetData netdata = null;
            if (_netdataProvider != null)
            {
                netdata = _netdataProvider.GetSocketNetData(dataSize);
                if (netdata.Data == null || netdata.Data.Length < dataSize)
                {
                    LogError("SocketNetData.Data 为空或长度出错");
                    netdata.Data = new byte[dataSize];
                }
            }
            else
            {
                netdata = new SocketNetData
                {
                    Size = dataSize,
                    Data = new byte[dataSize]
                };
            }
            _recvBuffer.Read(netdata.Data, dataSize);
            _recvBuffer.Delete(dataSize);
            lock (_recvList)
            {
                _recvList.Enqueue(netdata);
            }
            return netdata;
        }

        private void OnNetwork()
        {
            if (!this.ThreadMode)
            {
                if (_sendList.Count > 0)
                {
                    BeginSend();
                }
                BeginRecevice();
            }
            lock (_recvList)
            {
                while (_recvList.Count > 0)
                {
                    var d = _recvList.Dequeue();
                    try
                    {
                        OnReceiveData?.Invoke(d);
                        _netdataProvider?.ReleaseSocketNetData(d);
                    }
                    catch (Exception e)
                    {
                        LogError("OnNetwork RecvError" + e.ToString());
                    }
                }
            }
            if (_errorCode != ErrorCode.None)
            {
                return;
            }
        }

        private void BeginSend()
        {
            if (_sending) return;
            if (!_socket.Connected)
            {
                LogError("_socket.Connected is false");
                OnSocketError(ErrorCode.SendError);
                return;
            }
            if (_socket.GetStream() == null)
            {
                LogError("_socket.GetStream() is null");
                OnSocketError(ErrorCode.SendError);
                return;
            }

            while (_sendList.Count > 0)
            {
                var d = _sendList.Peek();
                var size = d.Size;
                if (_sendBuffer.Length > 0 && _sendBuffer.Length >= SendSize)
                {
                    break;
                }
                d = _sendList.Dequeue();
                _sendBuffer.Write(d.Data, size);
                OnSendData?.Invoke(d);
            }
            try
            {
                if (m_sendBuffer.Length < _sendBuffer.Length)
                {
                    m_sendBuffer = new byte[_sendBuffer.Length + 1024];
                }
                _sendBuffer.Read(m_sendBuffer, _sendBuffer.Length);
                _socket.GetStream().BeginWrite(m_sendBuffer, 0, _sendBuffer.Length, new AsyncCallback(EndSend), this);
                _sendBuffer.Delete(_sendBuffer.Length);
            }
            catch (Exception ex)
            {
                _sending = false;
                LogError(ex.ToString());
                OnSocketError(ErrorCode.SendError);
            }
        }
        private void EndSend(IAsyncResult asyncResult)
        {
            _sending = false;
            try
            {
                _socket.GetStream().EndWrite(asyncResult);
            }
            catch (Exception ex)
            {
                LogError(ex.ToString());
                OnSocketError(ErrorCode.SendError);
            }
        }

    }
    public partial class SocketClient
    {
        private Thread senderThread;
        CancellationTokenSource sendCts;
        private void StopSenderThread()
        {
            if (sendCts != null)
            {
                sendCts.Cancel();
                sendCts = null;
            }
            this.senderThread = null;
        }

        private void StartSenderThread()
        {
            StopSenderThread();

            sendCts = new CancellationTokenSource();
            this.senderThread = new Thread(() => { SenderThread(sendCts.Token); })
            {
                IsBackground = true
            };
            this.senderThread.Start();
        }

        private void SenderThread(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                if (this._socket.Connected == false || _socket.GetStream() == null)
                {
                    LogError("SenderThread error " + this._socket.Connected + ":" + (_socket.GetStream() == null));
                    break;
                }
                int sendLength = 0;
                lock (_sendList)
                {
                    while (_sendList.Count > 0)
                    {
                        var d = _sendList.Peek();
                        var size = d.Size;
                        if (_sendBuffer.Length > 0 && _sendBuffer.Length + size >= SendSize)
                        {
                            break;
                        }
                        d = _sendList.Dequeue();
                        _sendBuffer.Write(d.Data, size);
                    }
                    sendLength = _sendBuffer.Length;
                    if (m_sendBuffer.Length < sendLength)
                    {
                        m_sendBuffer = new byte[sendLength + 1024];
                    }
                    _sendBuffer.Read(m_sendBuffer, _sendBuffer.Length);
                    _sendBuffer.Delete(_sendBuffer.Length);
                }
                if (sendLength < 1)
                {
                    continue;
                }

                try
                {
                    _socket.GetStream().Write(m_sendBuffer, 0, sendLength);
                }
                catch (Exception)
                {
                    OnSocketError(ErrorCode.SendError);
                    //Close();
                }
                Thread.Sleep(50);
            }
        }
    }
    public partial class SocketClient
    {
        private Thread receiverThread;
        CancellationTokenSource recvCts;
        private byte[] _swapBuffer = new byte[1024 * 1024];
        private void StopReceiverThread()
        {
            if (recvCts != null)
            {
                recvCts.Cancel();
                recvCts = null;
            }
            this.receiverThread = null;
        }

        private void StartReceiverThread()
        {
            StopReceiverThread();
            recvCts = new CancellationTokenSource();
            this.receiverThread = new Thread(() => { Receive(recvCts.Token); })
            {
                IsBackground = true
            };
            this.receiverThread.Start();
        }
        private void Receive(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                if (this._socket.Connected == false || _socket.GetStream() == null)
                {
                    LogError("ReceiverThread error " + this._socket.Connected + ":" + (_socket.GetStream() == null));
                    break;
                }
                try
                {
                    if (_socket.GetStream().CanRead)
                    {
                        var length = _socket.GetStream().Read(this.socket_recvBuffer, 0, this.socket_recvBuffer.Length);
                        if (length > 0)
                        {

                            ReceiveData(length);
                        }
                    }
                }
                catch (Exception e)
                {
                    LogError(e.ToString());
                    OnSocketError(ErrorCode.ReceiveError);
                    //Close();
                }
                Thread.Sleep(10);
            }
        }
        private void ReceiverThread(CancellationToken cancellationToken)
        {
            int nPos = 0;
            int nBufCurLength = 0;
            bool sizeError = false;
            while (!cancellationToken.IsCancellationRequested)
            {
                if (this._socket.Connected == false || _socket.GetStream() == null)
                {
                    LogError("ReceiverThread error " + this._socket.Connected + ":" + (_socket.GetStream() == null));
                    break;
                }
                try
                {
                    if (_socket.GetStream().CanRead)
                    {

                        var bufSize = _socket.GetStream().Read(this.socket_recvBuffer, nBufCurLength, this.socket_recvBuffer.Length - nBufCurLength);
                        bufSize += nBufCurLength;
                        while (true)
                        {
                            int packageSize = 0;
                            if (bufSize - nPos >= 4)
                            {
                                packageSize = BitConverter.ToInt32(this.socket_recvBuffer, nPos);
                                if (packageSize > 1024 * 32 || packageSize < 1)
                                {
                                    if (!sizeError)
                                    {
                                        sizeError = true;
                                    }
                                }
                            }

                            if (bufSize - nPos >= packageSize && packageSize > 0)
                            {
                                var netdata = new SocketNetData
                                {
                                    Size = packageSize - 4,
                                    Data = new byte[packageSize - 4]

                                };
                                System.Buffer.BlockCopy(this.socket_recvBuffer, nPos + 4, netdata.Data, 0, packageSize - 4);
                                lock (_recvList)
                                {
                                    _recvList.Enqueue(netdata);
                                }
                                nPos += packageSize;

                            }

                            if (packageSize == 0 || bufSize - nPos < packageSize)
                            {
                                nBufCurLength = bufSize - nPos;
                                if (nBufCurLength > 0)
                                {
                                    System.Buffer.BlockCopy(this.socket_recvBuffer, nPos, _swapBuffer, 0, nBufCurLength);
                                    System.Buffer.BlockCopy(_swapBuffer, 0, this.socket_recvBuffer, 0, nBufCurLength);
                                }
                                nPos = 0;
                                break;
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    LogError(e.ToString());
                    OnSocketError(ErrorCode.ReceiveError);
                    //Close();
                }
                Thread.Sleep(10);
            }
        }
    }
}